#include "nemu.h"
#include "device/mmio.h"

#define PMEM_SIZE (128 * 1024 * 1024)

#define pmem_rw(addr, type) *(type *)({\
    Assert(addr < PMEM_SIZE, "physical address(0x%08x) is out of bound", addr); \
    guest_to_host(addr); \
    })

uint8_t pmem[PMEM_SIZE];

/* Memory accessing interfaces */

uint32_t paddr_read(paddr_t addr, int len) {
  int map_NO = is_mmio(addr);
	if (map_NO != -1) {
		return mmio_read(addr, len, map_NO);
	}
	else {
		return pmem_rw(addr, uint32_t) & (~0u >> ((4 - len) << 3));
	}
}

void paddr_write(paddr_t addr, int len, uint32_t data) {
	int map_NO = is_mmio(addr);
	if (map_NO != -1) {
		mmio_write(addr, len, data, map_NO);
	}
  else {
		memcpy(guest_to_host(addr), &data, len);
	}
}

/* 虚拟地址到物理地址的转换函数
 * addr: 虚拟地址
 * iswrite: 是否为写操作
 * 返回: 物理地址
 */
uint32_t page_translate(vaddr_t addr, bool iswrite) {
    // 检查分页机制是否启用（CR0.PG位）
    if (cpu.PG == 1) {
        // 页目录基地址从CR3寄存器获取
        paddr_t pde_base = cpu.cr3;
        // 计算页目录项(PDE)地址：
        // 虚拟地址高10位作为索引，乘以4（每个PDE占4字节）
        paddr_t pde_address = pde_base + ((addr >> 22) << 2);
        // 读取PDE内容
        paddr_t pde = paddr_read(pde_address, 4);
        // 检查PDE存在位（第0位）
        if (!(pde & 0x1)) {
            assert(0); // 触发页错误异常
        }
        // 从PDE中提取页表基地址（高20位）
        paddr_t pte_base = pde & 0xfffff000;
        // 计算页表项(PTE)地址：
        // 虚拟地址中间10位作为索引，乘以4（每个PTE占4字节）
        paddr_t pte_address = pte_base + ((addr & 0x003ff000) >> 10);
        paddr_t pte = paddr_read(pte_address, 4);
        // 检查PTE存在位
        if (!(pte & 0x1)) {
            assert(0); // 触发页错误异常
        }
        // 计算物理地址：PTE基地址 + 页内偏移（低12位）
        paddr_t page_address = (pte & 0xfffff000) + (addr & 0xfff);
        
        // 更新访问标志位（Accessed, 第5位）
        pde |= 0x20;
        // 如果是写操作，更新脏标志位（Dirty, 第6位）
        if (iswrite) {
            pde |= 0x40;
            pte |= 0x40;
        }
        // 写回更新后的PDE和PTE（模拟硬件自动设置标志位）
        paddr_write(pde_address, 4, pde);
        paddr_write(pte_address, 4, pte);
    
        return page_address;
    } else {
        // 分页未启用，直接返回虚拟地址作为物理地址
        return addr;
    }
}

/* 从虚拟地址读取数据
 * addr: 虚拟地址
 * len: 读取字节数
 * 返回: 读取到的数据
 */
uint32_t vaddr_read(vaddr_t addr, int len) {
    // 检查是否跨页（地址末尾12位 + len超过4KB）
    if (((addr & 0xfff) + len) > 0x1000) {
        int point; // 分割点
        paddr_t paddr, low, high;
        // 计算跨页分割点（超出部分的大小）
        point = (int)((addr & 0xfff) + len - 0x1000);
        // 处理低地址部分（前len-point字节）
        paddr = page_translate(addr, false);
        low = paddr_read(paddr, len - point);
        // 处理高地址部分（后point字节）
        paddr = page_translate(addr + len - point, false);
        high = paddr_read(paddr, point);
        // 合并两部分数据
        return (high << ((len - point) << 3)) | low;
    } else {
        // 单页内读取，直接转换并读取物理地址
        paddr_t paddr = page_translate(addr, false);
        return paddr_read(paddr, len);
    }
}

/* 向虚拟地址写入数据
 * addr: 虚拟地址
 * len: 写入字节数
 * data: 待写入数据
 */
void vaddr_write(vaddr_t addr, int len, uint32_t data) {
    // 检查是否跨页
    if (((addr & 0xfff) + len) > 0x1000) {
        int point;
        uint32_t low, high;
        paddr_t paddr;
        // 计算分割点
        point = (int)((addr & 0xfff) + len - 0x1000);
        // 分割数据为低位和高位部分
        low = data & ((1 << ((len - point) << 3)) - 1);
        high = data >> ((len - point) << 3);
    
        // 写入低地址部分
        paddr = page_translate(addr, true);
        paddr_write(paddr, len - point, low);
        // 写入高地址部分
        paddr = page_translate(addr + len - point, true);
        paddr_write(paddr, point, high);
    } else {
        // 单页内写入，直接转换并写入物理地址
        paddr_t paddr = page_translate(addr, true);
        paddr_write(paddr, len, data);
    }
}

